#include <Arduino.h>
#include <DS1302.h>

#include "Brightness.h"
#include "ESP8266_MQTT.h"
#include "Mode.h"
#include "TimeRange.h"

// 引脚的定义
const int lightSensorPin = A0;  // 光敏电阻连接的模拟输入端口
const int lightPin = 11;        // LED连接的PWM输出端口
const int pirPin = 10;          // 定义PIR传感器连接的数字引脚

// 计时模块DS1302相关定义
DS1302 rtc(2, 3, 4);  // 对应DS1302的RST,DAT,CLK

// 超声波模块引脚定义
const int trigPin = 13;  // 超声波Trig引脚
const int echoPin = 12;  // 超声波Echo引脚

// WiFi 配置
const char* ssid = "xiaohui";
const char* password = "mhz6677514";
// MQTT 配置
const char* mqtt_server = "122.51.61.125";  // 可以是 IP 或域名
const char* mqtt_user =
    "smart_street_lighting_device_01";           // 如果没有用户名，留空
const char* mqtt_password = "vcxz1234fdsaqwer";  // 如果没有密码，留空
const char* client_id = "smart_street_lighting_device_01";  // 客户端 ID
const char* sub_topic = "ssl/app/#";                        // 订阅的主题
// 发布的主题
const char* pub_topic_mode = "ssl/device/mode";
const char* pub_topic_brightness = "ssl/device/brightness";
const char* pub_topic_time_range = "ssl/device/time_range";

// 函数声明
void timedLight();
void autoLight();
void turnWithBrightness(Brightness brightness);
void initRTCTime();
void initESP8266();
void turnOn();
void turnOff();
void mqttHandler();
int getDistance();

// 定时开关时段, 默认时段：从 18:00:00 到 06:00:00 (跨午夜)
TimeRange timeRange({18, 0, 0}, {6, 0, 0});

// 记录当前模式
Mode curLightMode = Mode::Automatic;
// 记录当前路灯开关
bool curLightSwitch = false;
// 记录当前路灯亮度
Brightness curLightBrightness = Brightness::OFF;

// 创建 ESP8266_MQTT 对象
ESP8266_MQTT esp8266(8, 9, ssid, password);  // RX, TX, wifi ssid, wifi 密码

// 新增超声波相关参数
const int detectionDistance = 30;  // 检测距离阈值（厘米）

void setup() {
    Serial.begin(9600);  // 初始化电脑串口

    pinMode(lightSensorPin, INPUT);  // 设置光敏电阻端口为输入模式
    pinMode(lightPin, OUTPUT);       // 设置LED端口为输出模式
    pinMode(pirPin, INPUT);          // 设置PIR引脚为输入模式
    // 超声波引脚初始化
    pinMode(trigPin, OUTPUT);
    pinMode(echoPin, INPUT);

    // initRTCTime(); // 初始化时钟一次就好

    initESP8266();  // 初始化esp8266
}

void loop() {
    // 根据当前照明模式控制灯光
    switch (curLightMode) {
        case Automatic:
            autoLight();
            break;
        case Timed:
            timedLight();
            break;
        default:
            break;
    }

    // 处理App端发来的消息
    mqttHandler();

    delay(100);
};

/**
 * MQTT处理函数
 *
 * 该函数用于处理接收到的MQTT消息它根据消息的主题和负载执行不同的操作
 * 主要功能包括：更改模式、调整亮度和设置时间范围
 */
void mqttHandler() {
    static String topic, payload;
    if (esp8266.listen(topic, payload)) {
        if (topic.endsWith("mode")) {
            int mode = payload.toInt();
            if (mode >= 1 && mode <= 3) {
                curLightMode = static_cast<Mode>(mode);
                // 更新模式后先熄灭路灯
                turnOff();
                esp8266.publish(pub_topic_mode, payload.c_str(), 0, 1);
                // Serial.print("change mode: ");
                // Serial.println(mode);
            };
        } else if (topic.endsWith("brightness") &&
                   curLightMode == Mode::Manual) {
            // 只能在Manual模式才能控制亮度
            Brightness brightness = getBrightness(payload.toInt());
            turnWithBrightness(brightness);
            // Serial.print("change brightness: ");
            // Serial.println(static_cast<int>(brightness));
        } else if (topic.endsWith("time_range")) {
            // time_range 格式:"183030;063030", 代表:从18:30:30到06:30:30
            // 开始时间字符串和结束时间字符串由分号分割
            int semPos = payload.indexOf(';');
            if (semPos != -1) {
                String start_str = payload.substring(0, semPos);
                String end_str = payload.substring(semPos + 1);

                // 把字符串解析TimeRange::Time类型
                TimeRange::Time start = TimeRange::Time::formString(start_str);
                TimeRange::Time end = TimeRange::Time::formString(end_str);

                // 更新时段
                timeRange.updateTimeRange(start, end);
                esp8266.publish(pub_topic_time_range, payload.c_str(), 0, 1);

                // char buf[20];
                // snprintf(buf, sizeof(buf), "%s ~ %s",
                // start.toString().c_str(),
                //          end.toString().c_str());
                // Serial.print("change time range: ");
                // Serial.println(buf);
            }
        }
    }
}

// 超声波测距函数
int getDistance() {
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    long duration = pulseIn(echoPin, HIGH);
    return duration * 0.034 / 2;  // 转换为厘米
}

/**
 * 自动开关路灯
 *
 * 根据红外、超声波模块检测人体决定是否开灯
 * 如果检测到人体，则根据光敏电阻的模拟值调整亮度级别
 */
void autoLight() {
    static boolean detected = false;

    // 避免检测过于频繁
    static unsigned long lastDetection = 0;
    if (millis() - lastDetection < 800) {
        return;
    }
    lastDetection = millis();

    int distance = getDistance();

    if (digitalRead(pirPin)) {
        Serial.print("检测到行人, PWM: ");
        detected = true;
    } else if (distance < detectionDistance) {
        Serial.print("检测到车辆, 距离: ");
        Serial.print(distance);
        Serial.print(", PWM: ");
        detected = true;
    } else {
        detected = false;
    }

    if (detected) {
        // 读取光敏电阻的模拟值（0 - 1023）
        int lsVal = analogRead(lightSensorPin);

        // 调整映射关系
        int pwmVal = map(lsVal, 0, 1023, 0, 255);
        Serial.println(pwmVal);

        // 根据映射后的PWM值选择亮度级别
        Brightness brightness = getBrightness(pwmVal);

        turnWithBrightness(brightness);
    } else {
        turnOff();
    }
};

/**
 * 定时开关路灯
 *
 * 根据设定的时间范围，定时开关路灯
 */
void timedLight() {
    static uint8_t last_second = 0;
    Time time = rtc.time();
    // 如果秒数发生更新
    if (last_second != time.sec) {
        last_second = time.sec;
        char buf[20];
        snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d", time.yr,
                 time.mon, time.date, time.hr, time.min, time.sec);
        Serial.println(buf);

        // 检查当前时间是否在范围内
        bool shouldTurnOn =
            timeRange.isWithinRange(time.hr, time.min, time.sec);

        // 仅在状态变化时更新灯光
        if (shouldTurnOn != curLightSwitch) {
            shouldTurnOn ? turnOn() : turnOff();
        }
    }
};

/**
 * 根据亮度级别打开或关闭路灯
 *
 * @param brightness 亮度级别
 */
void turnWithBrightness(Brightness brightness) {
    if (curLightBrightness == brightness) return;

    int bn = static_cast<int>(brightness);
    // 输出PWM信号控制路灯亮度
    analogWrite(lightPin, bn);

    curLightSwitch = (brightness == Brightness::OFF) ? false : true;
    curLightBrightness = brightness;

    esp8266.publish(pub_topic_brightness, brightnessToStr(brightness), 0, 1);
}

// 打开路灯(FULL_BRIGHT)
void turnOn() {
    if (curLightBrightness == Brightness::FULL_BRIGHT) return;

    digitalWrite(lightPin, HIGH);
    curLightSwitch = true;
    curLightBrightness = Brightness::FULL_BRIGHT;

    esp8266.publish(pub_topic_brightness, brightnessToStr(curLightBrightness),
                    0, 1);
}

// 关闭路灯(OFF)
void turnOff() {
    if (curLightBrightness == Brightness::OFF) return;

    digitalWrite(lightPin, LOW);
    curLightSwitch = false;
    curLightBrightness = Brightness::OFF;

    esp8266.publish(pub_topic_brightness, brightnessToStr(curLightBrightness),
                    0, 1);
}

// 初始化时钟
void initRTCTime() {
    rtc.writeProtect(false);  // 去掉写保护
    rtc.halt(false);          // 清除时钟停止标志
    Time t(2025, 1, 22, 13, 27, 0, Time::kTuesday);
    rtc.time(t);
}

// 初始化esp8266
void initESP8266() {
    // 在 WiFi 模块发送给 arduino UNO 板的打印信息出现了乱码，
    // 是因为软串口（i/o 模拟出来）所能支持的波特率有限，最大支持 9600
    // 通过 AT 指令设置为 9600,指令为：AT+UART=9600,8,1,0,0
    esp8266.begin(9600);  // 初始化 ESP8266 串口

    esp8266.sendATCommand("AT", 2000);

    // 连接 WiFi
    esp8266.connectWiFi();

    // 配置 MQTT 用户信息
    esp8266.configMQTTUser(client_id, mqtt_user, mqtt_password);

    // 连接 MQTT 服务器
    esp8266.connectMQTT(mqtt_server, 1883);

    // 订阅主题
    esp8266.subscribe(sub_topic, 1);
}
